using System;
using System.Xml;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Globalization;

namespace Dida
{
	[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
	public class FromSettingsAttribute : Attribute
	{
	}

	/// <summary>
	/// The loader for a single setting, defined it the xml file we are loading.
	/// When the setting is in this class, it doesn't belong to a settings 
	/// container yet.
	/// </summary>
	internal class SettingLoader
	{
		enum State
		{
			UnparsedSimple,
			ParsedSimple,
			Complex,
		}
		string name;
		State state;
		object value;
		bool used;

		XmlPositionMarker xmlPosition;

		public SettingLoader(string name,XmlReader reader)
		{
			this.name = name;
			used = false;
			xmlPosition = new XmlPositionMarker(reader);

			if (reader.NodeType == XmlNodeType.Element)
			{
				// it's a complex type

				throw new NotImplementedException();
			}
			else
			{
				// it's a simple type
				value = reader.ReadString();
				state = State.UnparsedSimple;
			}
		}

		// TODO: make this advanced
		public object Process(Type type)
		{
			used = true;

			if (state == State.UnparsedSimple)
			{
				if (type == typeof(float))
				{
					value = float.Parse((string)value, CultureInfo.InvariantCulture);
					state = State.ParsedSimple;
					return value;
				}
				else if (type.IsEnum)
				{
					FieldInfo[] fields = type.GetFields(BindingFlags.Static);
					foreach (FieldInfo field in fields)
						if (field.Name == (string)value)
						{
							value = field.GetValue(null);
							state = State.ParsedSimple;
							return value;
						}

					throw new XmlReaderException("\"" + value + "\" isn't a " +
						type.Name, xmlPosition);
				}
				else
				{
					throw new XmlReaderException("Unsupported setting type: " +
						type.FullName, xmlPosition);
				}
			}

			if (state == State.ParsedSimple)
			{
				if (type.IsInstanceOfType(value))
					return value;
				else
					throw new Exception("Conflicting settings.");
			}

			if (state == State.Complex)
			{
				if (type.IsInstanceOfType(value))
					return value;
				else
				{
					throw new XmlReaderException("The setting \"" + name +
						"\" must be of type " + type.Name, xmlPosition);
				}
			}

			Debug.Fail("Invalid state");
			return null;
		}

		public string Name
		{
			get { return name; }
		}

		public XmlPositionMarker XmlPosition
		{
			get { return xmlPosition; }
		}

		public bool Used
		{
			get { return used; }
		}
	}

	/// <summary>
	/// This class is used to load settings, in a scope-structured way. This scope
	/// mechanism allows you to define the common settings globally, while other
	/// settings, which are more specific to a single object, can be defined locally.
	/// </summary>
	/// <remarks>
	/// Examples of settings that would most likely be global
	/// <list>
	/// <item>Gravity for jumping</item>
	/// <item>Walk speed</item>
	/// </list>
	/// Examples of settings that may be overridden in deeper scopes:
	/// <item>The initial velocity of a jump - this may be different depending on
	/// how the jump was instigated. For example, when you press the 
	/// jump button, it may be 5, while if you end up on a jumppad, it
	/// may be a lot higher. So in the scope of the jump button, the initialSpeed setting
	/// will be 5, while in case of the jumppad, that same setting will 
	/// have a different value</item>
	/// </remarks>
	public class ScopedSettingsLoader
	{
		Scope globalScope;
		Scope curScope;

		Dictionary<Type, SettingsContainerInfo> defaultsOnlyContainers = 
			new Dictionary<Type,SettingsContainerInfo>();
		List<SettingsContainerInfo> flattenedList = new List<SettingsContainerInfo>();

		public ScopedSettingsLoader()
		{
			globalScope = new Scope();
			curScope = globalScope;
		}

		/// <summary>
		/// Reads a settings block into the current scope.
		/// </summary>
		/// <remarks>
		/// The XmlReader is expected to be at the beginning of an element
		/// that contains the settings as child elements. When this method
		/// returns, the XmlReader is after the closing tag of the settings block.
		/// </remarks>
		/// <param name="reader">The xml reader, that's at the 
		/// beginning of the settings block.</param>
		public void LoadIntoCurScope(XmlReader reader)
		{
			if (reader.IsEmptyElement)
			{
				// it doesn't contain any settings, but an empty settings block
				// is not illegal, so we can just return immediately
				reader.ReadStartElement();
				return;
			}

			reader.ReadStartElement();
			curScope.LoadSettings(reader);
			reader.ReadEndElement();
		}

		/// <summary>
		/// Makes the child scope of the current scope, that has the given name,
		/// the current scope. If the scope doesn't exist yet, a new scope is created.
		/// </summary>
		public void PushScope(string name)
		{
			Scope newScope = curScope.GetChildScope(name);
			if (newScope == null)
			{
				newScope = new Scope(name);
				curScope.AddChildScope(newScope);
			}

			curScope = newScope;
		}

		/// <summary>
		/// Pops the current scope from the scope stack, so it's parent scope
		/// will become the current scope again.
		/// </summary>
		public void PopScope()
		{
			Debug.Assert(curScope != globalScope, "You can't pop the global scope");

			curScope = curScope.ParentScope;
		}

		/// <summary>
		/// Creates a SettingsContainer of the given type. Resolving the settings
		/// is done regarding the current scope.
		/// </summary>
		/// <param name="containerType">The type of which the settings container
		/// you want to use. This type must be a type that's derived from 
		/// the SettingsContainer class.</param>
		/// <returns>The SettingsContainer that contains the settings.
		/// The returned object is an instance of the given, so you can cast it to
		/// that type.</returns>
		public SettingsContainerInfo GetSettings(Type containerType)
		{
			Debug.Assert(containerType.IsSubclassOf(typeof(SettingsContainer)),
				"The type of a settings container must derive from SettingsContainer");

			IList<string> settingNames = SettingsContainer.GetFlattenedSettingNames(containerType);
			if (settingNames.Count == 0)
			{
				// if there are no settings, we just create in instance of the 
				// given container type, and don't resolving any settings.
				return GetDefaultsOnlyInstance(containerType);
			}

			// find the scope height of the deepest scope that defines (overloads) 
			// a setting that's used in this settings container. This scope is the
			// place where we can define our unqiue settings instance, while also
			// enabling other scopes, that don't overload any settings from this 
			// scope either, to find the instance aswell.
			int height = int.MaxValue;
			foreach (string settingName in settingNames)
			{
				int curHeight = curScope.GetSourceHeight(settingName);
				if (curHeight != -1 && curHeight < height)
					height = curHeight;
			}

			// if not a single value is resolved (so all values of the settings
			// struct will have their default value defined in code),
			// we will create a fresh instance
			if (height == int.MaxValue)
			{
				return GetDefaultsOnlyInstance(containerType);
			}
			else
			{
				Scope resolvingScope = curScope.GetScopeAtHeight(height);
				return resolvingScope.GetSettings(containerType,flattenedList);
			}
		}

		private SettingsContainerInfo GetDefaultsOnlyInstance(Type containerType)
		{
			SettingsContainerInfo ret;
			if (defaultsOnlyContainers.TryGetValue(containerType, out ret))
			{
				return ret;
			}
			else
			{
				ret = new SettingsContainerInfo();
				ret.settingsContainer = (SettingsContainer)
					Activator.CreateInstance(containerType);
				ret.index = flattenedList.Count;
				flattenedList.Add(ret);

				foreach (SettingsContainer.Accessor accessor in 
					ret.settingsContainer.Accessors)
				{
					if (accessor.SettingType.IsSubclassOf(typeof(SettingsContainer)))
					{
						accessor.Value = GetDefaultsOnlyInstance(accessor.SettingType);
					}
				}

				defaultsOnlyContainers[containerType] = ret;
				return ret;
			}
		}

		public object GetSetting(string name, Type settingType)
		{
			return curScope.GetSettingRecursive(name, settingType);
		}

		public List<Exception> UnusedSettingsErrorList
		{
			get
			{
				List<Exception> errorList = new List<Exception>();
				globalScope.GetUnsedSettingsErrorList(errorList);
				return errorList;
			}
		}

		public IList<SettingsContainerInfo> FlattendSettingsList
		{
			get 
			{
				return flattenedList; 
			}
		}

		public class SettingsContainerInfo
		{
			public SettingsContainer settingsContainer;
			public int index = -1;
		}

		private class Scope
		{
			string name;
			Scope parentScope = null;
			Dictionary<string, Scope> childScopes = new Dictionary<string, Scope>();

			Dictionary<string, SettingLoader> settings =
				new Dictionary<string, SettingLoader>();

			Dictionary<Type, SettingsContainerInfo> loadedContainers = 
				new Dictionary<Type, SettingsContainerInfo>();

			public Scope()
			{
				name = null;
			}

			public Scope(string name)
			{
				this.name = name;
			}

			public void AddChildScope(Scope scope)
			{
				Debug.Assert(!childScopes.ContainsKey(scope.name),
					"There's already a scope with the given name");

				childScopes[scope.name] = scope;
				scope.parentScope = this;
			}

			public Scope ParentScope
			{
				get { return parentScope; }
			}

			public Scope GetChildScope(string name)
			{
				Scope ret;
				if (childScopes.TryGetValue(name, out ret))
					return ret;
				else
					return null;
			}

			public void LoadSettings(XmlReader reader)
			{
				while (reader.NodeType == XmlNodeType.Element)
				{
					string settingName = reader.Name;

					if (settings.ContainsKey(settingName))
					{
						throw new XmlReaderException("The setting " + reader.Name + 
							" was already been specified earlier in this " + 
							"settings block", reader);
					}

					bool isEmptyElement = reader.IsEmptyElement;
					string name = reader.Name;
					reader.ReadStartElement();
					settings[settingName] = new SettingLoader(name,reader);
					if (!isEmptyElement)
						reader.ReadEndElement();
				}
			}

			/// <summary>
			/// Gets the 'height' of the setting that would resolve the given name,
			/// from the current scope. The height is the number of scopes we 
			/// have to go up to find the definition. This means that if it's
			/// defined in this scope, the return value will be 0.
			/// </summary>
			/// <param name="srcName">The name we are trying to resolve</param>
			/// <returns>The height of the setting that would resolves the
			/// given name, or -1 if the setting isn't found</returns>
			public int GetSourceHeight(string srcName)
			{
				if (settings.ContainsKey(srcName))
					return 0;
				else if (parentScope != null)
				{
					int ret = parentScope.GetSourceHeight(srcName);
					if (ret != -1)
						return ret + 1;
					else
						return -1;
				}
				else
					return -1;
			}

			public Scope GetScopeAtHeight(int height)
			{
				if (height == 0)
					return this;
				else
				{
					Debug.Assert(parentScope != null, "Invalid height");

					return parentScope.GetScopeAtHeight(height - 1);
				}
			}

			public string GetQualifiedSettingName(string baseName)
			{
				string prefix = null;
				for (Scope scope = this; scope != null; scope = scope.parentScope)
				{
					if (scope.name != null)
						prefix = scope.name + "." + prefix;
				}

				if (prefix != null)
					prefix += ".";

				return prefix + baseName;
			}

			public SettingsContainerInfo GetSettings(Type type,List<SettingsContainerInfo> flattened)
			{
				return GetSettingsImpl(type, this,flattened);
			}

			private SettingsContainerInfo GetSettingsImpl(
				Type type, Scope startScope,List<SettingsContainerInfo> flattened)
			{
				SettingsContainerInfo ret;
				if (loadedContainers.TryGetValue(type, out ret))
				{
					return ret;
				}
				else
				{
					ret = new SettingsContainerInfo();
					ret.settingsContainer = (SettingsContainer)Activator.CreateInstance(type);
					ret.index = flattened.Count;

					loadedContainers[type] = ret;
					flattened.Add(ret);

					foreach (SettingsContainer.Accessor accessor in
						ret.settingsContainer.Accessors)
					{
						if (accessor.SettingType.IsSubclassOf(typeof(SettingsContainer)))
						{
							accessor.Value = startScope.GetSettings(accessor.SettingType,flattened);
						}
						else
						{
							object value = GetSettingRecursive(
								accessor.Name, accessor.SettingType);
							// if the setting isn't found, we just leave
							// it at it's default value set in code.
							if (value != null)
							{
								accessor.Value = value;
							}
						}
					}

					return ret;
				}
			}

			public object GetSettingRecursive(string name, Type type)
			{
				SettingLoader setting;
				if (settings.TryGetValue(name, out setting))
				{
					return setting.Process(type);
				}
				else
				{
					if (parentScope != null)
					{
						return parentScope.GetSettingRecursive(name, type);
					}
					else
						return null;
				}
			}

			public void GetUnsedSettingsErrorList(List<Exception> errorList)
			{
				foreach (SettingLoader setting in settings.Values)
				{
					if (!setting.Used)
					{
						errorList.Add(new XmlReaderException("Unknown setting: \"" +
							setting.Name + "\"", setting.XmlPosition));
					}
				}

				foreach (Scope child in childScopes.Values)
					child.GetUnsedSettingsErrorList(errorList);
			}
		}
	}
}